Behind the bandmap scale, the CW part of the band is one shade and the phone part another, with a third for a digital block. That is what N1MM's graphical bandmap does: ResizeBandPanels puts a CW, a Digi and an SSB panel behind the scale, sized from its CWBands, DigiBands and SSBBands tables. Anything past a band edge is shaded red, which is the only place the edges themselves show, and it only appears now that the slice can reach past an edge. The defaults are N1MM's: CW ends at 1840, 3600, 7125, 14150 and so on, and the digital pair is empty until somebody fills it in, exactly as N1MM ships. They are one worldwide set rather than one per ITU region, which is also N1MM's answer — the reason recorded for leaving this out, that the segments differ by region, was wrong about what N1MM holds. 60M and everything above 2M get no entry, because N1MM's own numbers there contradict themselves: its 1.25M CW top is below its phone start, and its 70CM CW top is above the band. Config ▸ Sub bands edits them, one row per band in kilohertz, with a Defaults button. Only the rows that differ are written to the settings file, so a plan nobody has touched leaves nothing behind. BandPlan is an object rather than a static table, so the stored changes layer over the defaults and the bandmap picks up a save without a restart. The numbers are a drawing aid, not a licence. Nothing stops a contact being logged either side of a boundary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nonemm
A contest logger for amateur radio: a from-scratch reimplementation of N1MM Logger+ in C#, running on Linux and Windows.
The contest rules, log formats and database schema are written from their
published definitions. What Nonemm keeps is interoperability: the log
database is N1MM's .s3db in N1MM's schema, user-defined contests are N1MM's
.udc files, and a log either program writes opens in the other.
Running it
The SDK lives at ~/.dotnet on this machine, so a shell that has not been set
up needs:
export DOTNET_ROOT=$HOME/.dotnet PATH="$HOME/.dotnet:$PATH"
Then:
dotnet run --project src/Nonemm.App # start the logger
dotnet test Nonemm.slnx # run every test
./build.sh does the same with the environment already set: ./build.sh test Nonemm.slnx.
To work a contest: Config → Station for your callsign and zones, then File → New Database and File → New Contest. In the entry window, type a callsign and press space to move to the exchange, then Enter to log. Type a frequency into the callsign box and press Enter to change band. View opens the log, check, bandmap, score and packet windows.
Everything that judges a station — the bar under the callsign box, a row in the check window, a spot on the bandmap, a line in the log — is coloured by the same scorer: red for a dupe, green for a new multiplier, blue for points.
What it does
| Contests | CQ WW, CQ WPX, ARRL DX, IARU HF, Sweepstakes, RTTY Roundup, NAQP, general logging, and user-defined .udc contests |
| Log | N1MM .s3db, Cabrillo 3.0 out, ADIF in and out |
| While typing | dupe check, multiplier check, points, country and zone from the country file, exchange filled from a call history file |
| Windows | entry, log, check, bandmap, available mults and Qs, score summary, packet |
| Editing | double-click a cell in the log, or open the whole contact with Ctrl+Y; Delete removes it. All of it goes out to the other stations |
| Radio | one or two radios over hamlib rigctld, split, reconnecting on its own |
| Cluster | DX cluster over telnet, spots feeding the bandmap, Alt+P to spot a station |
| Bandmap | drawn like N1MM's: a frequency scale with the receiver on it and callsigns beside it, joined by leader lines |
| Network | contacts shared with the other stations of a multi-operator entry, in N1MM's own contact message |
| Keying | CW through cwdaemon or a WinKeyer, with N1MM's message macros |
The country file and the callsign database
Neither is bundled. Config → Download Country File and Download Check
Partial File fetch them from where they are published, which is where N1MM
fetches them from too —
country-files.com/cty/wl_cty.dat
and
supercheckpartial.com/MASTER.SCP.
The country file is wl_cty.dat rather than plain cty.dat: same format, with
the WAE entities listed separately, which is what CQ WW counts.
A download that fails changes nothing. The file is fetched, checked that it parses as what it claims to be, and only then put in place, so a site that answers with an apology page instead of a country file cannot cost an operator their multipliers mid-contest.
Both files can also be dropped into SupportFiles under the configuration
directory (~/.config/nonemm on Linux, Documents\Nonemm on Windows).
User-defined contests go in UserDefinedContests under the same directory.
Without a country file the program still runs; country- and continent-scored contests lose accuracy.
Radio control
The logger reads and tunes the radio through
hamlib's rigctld, started separately for whichever
radio is on the desk:
rigctld -m 2028 -r /dev/ttyUSB0 # -m is the hamlib model number; rigctl -l lists them
Then Config → Radio. With no radio connected nothing changes: frequency and mode stay where they were last typed.
CW
Config → Keyer and messages picks cwdaemon (a UDP port, usually 6789) or a
WinKeyer (a serial port), sets the speed, and edits the twelve function key
messages for CW and for phone. The macros are N1MM's: {MYCALL}, {CALL},
{EXCH}, {SENTRST}, {SENTNR}, # for the serial number, and {SENTRSTCUT}
for cut numbers. Escape stops sending.
Editing the log
Double-click a cell in the log window to change it. The columns follow the contest exchange, so CQ WW shows a Zone column and Sweepstakes shows Nr, Prec, Ck and Sec. A value the contest does not accept — zone 41, an ARRL section that does not exist, a callsign with a space in it — is refused and the reason appears under the log. Changing the callsign looks the country up again.
Delete, or the right-click menu, removes the selected contact after a confirmation. Either way the whole log is scored again, so a multiplier the removed contact was holding passes to the next contact that claims it.
Edit → Edit Last Contact, Ctrl+Y, or Enter on a row in the log opens the whole contact in one dialog. That is where the fields with no log column live: QSX frequency, name, QTH, comment, grid, power, radio number, run position, and the country and WPX prefixes. ◀ and ▶ walk the log without closing, offering to save first. Points and the multiplier flags are shown but cannot be typed in — they are worked out from the rules every time the log changes, so anything typed there would be overwritten on the next edit.
Correcting the country prefix by hand changes the score. The country file is a best guess for calls it has no rule for, so what the contact says now wins over what the file says.
Call history files
Config → Call History File… points at one of the files published before a
contest. It is N1MM's format, so a file written for N1MM is read as it is:
# comments, !!Order!! naming the columns of the lines after it, semicolons
in preference to commas, and the directives that change what is stored —
!!FourCharGridSq!!, !!NoLoc2AltGrid!!, !!MapStateToSect!!,
!!AppendUserText!! and !!NoAppendUserText!!.
Leaving the callsign box fills what the file knows into the exchange boxes that are still empty. A box with something in it is left alone: what the other station actually sends beats what somebody published months ago. The check window gets a History column of matching calls.
The section-validating directives — !!Validate50State!!,
!!ValidateArrlSection!!, !!MapOnSection!!, !!GTA2GH_NT2TER!! — are read
and passed over rather than acted on. Throwing data away on a section list this
program does not hold would be worse than keeping it.
The check window
Five columns, kept apart because they answer different questions: Log is what this station copied itself, Master is a guess about the whole world, History is what was published before the contest, Bandmap is somebody else's claim, and Exchange offers the values the exchange box with the cursor in it can hold — the ARRL sections, or the states and provinces. A serial number or a report has no list behind it, so that column stays empty rather than guessing.
Available mults and Qs
View → Available Mults and Qs lists what is spotted and not worked yet, a column per band, multipliers first and then the stations that are only points. Clicking one puts the radio there with the call in the entry window, the same as clicking a spot on the bandmap. Mults only hides everything that brings no multiplier.
A spot carries no exchange, so a station is only known to be a multiplier where
the multiplier follows from the callsign — a DXCC entity — or where the call
history file says what the station sends. That is why CQ WW shows the country
but not the zone: the zone that counts is the one the station sends, and the
country file's guess is wrong for the large countries. A call history file with
a CQZone column fills that in.
A spot carries no mode either. Stations are judged in the mode the radio is in, so in a mixed-mode contest the answer follows the operator.
Radios
Config → Radios takes a rigctld address per radio. Each radio needs its
own rigctld, started for whichever rig is on that port:
rigctld -m 2028 -r /dev/ttyUSB0 -t 4532
rigctld -m 1035 -r /dev/ttyUSB1 -t 4533
Commands go out with a + in front, which asks rigctld for its extended
answer: named fields ended by an RPRT line. The raw answer is bare values with
no terminator, so the client has to know how many lines each command returns,
and one wrong count leaves the connection reading every later answer against
the wrong command.
Split is read from the radio and recorded: the contact stores where we
transmitted in N1MM's QSX column, the entry window shows 14008.00 ▸ 14020.0,
and the bandmap draws a red bar at the transmit frequency. A radio that cannot
do split answers RPRT -11; that is an answer, not a broken connection, and
everything else it reported still counts.
A second radio makes the station SO2R and opens a second entry window. The two windows share one log, one score and one run of serial numbers, so a station worked on radio 1 is a dupe on radio 2. What is typed, the frequency, the mode and whether you are running belong to each radio on its own.
| Key | What it does |
|---|---|
| Ctrl+Tab | moves you to the other radio: the keyboard, the entry window and the box all follow |
| Ctrl+Shift+Tab | puts both radios in the headphones, and back |
Both radios show on the bandmap — the one you are on in green, the other in orange — but only the one you are on drives its entry window, so the second radio moving cannot drag you off the station you are working.
An SO2R box speaking OTRSP routes the key and the headphones. Name its serial
port in Config → Radios and it follows: TX2 when you move to radio 2,
RX1S when you ask for both radios, and TX again before every message goes
out, so a message cannot go out of the radio you have just left. A command that
would change nothing is not sent, because the box works relays. Without a box
the logger just keeps track of which radio it is on and you switch by hand.
Not there yet: alternating CQ, and voice keying on the second radio.
The bandmap
A frequency scale down the left with the stations written out beside it. Each callsign sits level with its frequency, and when two stations are too close to write one above the other the lower one moves down and a leader line runs back to where it really is. That is what N1MM does, and the stacking follows its rule: place the label centred on its frequency, and push it below the one above if it would cover it.
The slice shown follows the receiver — 10, 20, 40 or 100 kHz, or the whole band — and stretches past the band edge when the radio is past it. Callsigns are coloured by the same scorer as the entry window, so a dupe reads as a dupe here too. Clicking a callsign puts the radio there with the call already in the entry window; clicking anywhere else just moves the radio.
The bars on the scale are as wide as the mode passes: green where you are listening, red where the radio transmits when working split, orange for the other radio of a two-radio station.
Behind the scale the CW part of the band is one shade and the phone part
another, with a third shade for a digital block where one is set, and the CW
boundary named in the status line. Anything past a band edge is shaded red,
which is where the edges show. N1MM paints the same blocks from its CWBands,
DigiBands and SSBBands tables.
Config → Sub bands edits them, one row per band, in kilohertz, with a Defaults button. The digital pair starts empty, as N1MM's does, so nothing is painted for it until it is filled in. Only the rows that differ from the defaults are written to the settings file.
The defaults are one worldwide set, as N1MM's are, not one per ITU region. They are a drawing aid: a contact logs either side of a boundary just the same. Bands where N1MM's own numbers contradict themselves — 60M, and everything above 2M — get no shading rather than a guess.
Band edges
A contact just outside a band edge keeps its band. Bands.ForFrequency first
checks the real edges, and for anything outside them falls back to the whole
megahertz: 14400 kHz is 20M, 7305 kHz is 40M. This is N1MM's rule, and it is
there because contest operating goes past the edges — someone answers a CQ at
14352, or a radio reports a frequency a few hundred hertz off — and a contact
with no band scores nothing and exports wrong. Below 1 MHz the megahertz is
always 0, so 2190M and 630M are told apart by their edges alone.
The DX cluster
Config → Cluster takes the node's address, a password for the few nodes that ask for one, and the commands to send after login. Filters are the node's business, so whatever goes in the command box is sent as typed and left alone.
The client speaks telnet properly: it answers the option negotiation instead of
letting the control bytes turn up in the first lines of text, and it reads the
login prompt out of a partial line, because nodes write login: with no line
ending. If no prompt arrives within ten seconds the callsign goes out anyway,
which is what N1MM does. A connection that has heard nothing for four minutes
gets a blank line, so the node does not drop it as idle. A dropped connection is
retried.
Alt+P, or Edit → Spot It, puts the call being typed on the cluster at the current frequency; with nothing typed it spots the last contact logged. The spot also goes straight onto our own bandmap rather than waiting to come back round from the node.
Networked stations
Config → Network names this station and lists the others. Each contact is
sent to them as it is logged, in N1MM's contactinfo message, so an N1MM
station on the same network sees them too. Editing a contact sends
contactreplace and deleting one sends contactdelete, the same as N1MM. With
no addresses listed the messages are broadcast. A contact that arrives is scored
again here from the rules rather than trusted.
An incoming edit or delete is matched by contact id first. N1MM does not know our ids, so the call and the timestamp are the fallback — that is the pair N1MM itself keys a contact on.
Layout
| Project | What it holds |
|---|---|
Nonemm.Core |
Frequencies, bands, modes, callsigns, grid squares, the country file and the callsign database |
Nonemm.Contests |
Contest rules, the scoring engine, .udc files |
Nonemm.Formats |
Cabrillo out, ADIF in and out |
Nonemm.Storage |
The N1MM-compatible .s3db |
Nonemm.Rig |
Radio control over rigctld, and the SO2R box |
Nonemm.Spotting |
Spots, the bandmap, the DX cluster client |
Nonemm.Network |
Contacts shared between the stations of a multi-operator entry |
Nonemm.Keying |
CW through cwdaemon or a WinKeyer |
Nonemm.Session |
The contest in progress, and one entry position per radio — no UI toolkit |
Nonemm.App |
The Avalonia windows |
The split at Nonemm.Session is the important one: it references no UI
framework, so what space does, when a dupe fires and what a contact scores are
covered by plain unit tests.
Where it stands
Working: logging a contest end to end, live dupe and multiplier checking, eight built-in contests plus user-defined ones, Cabrillo and ADIF export, ADIF import, the log, check, graphical bandmap, score and packet windows, editing and deleting logged contacts, radio control, DX cluster spots, contacts shared between networked stations, and CW keying.
Checked against N1MM 1.0.11031: a log this program wrote opens in N1MM, which
reads the contest, its categories and the contacts. See
docs/n1mm-interop.md for what that took.
Nothing here has been run against a real radio, keyer, SO2R box or cluster node. Every one of those is tested against a fake that speaks the same protocol, which proves the two agree with each other and nothing more.
docs/unfinished.md lists what is half-built and what has
never been tested against the real thing.