ericek111 b9f0166777 Draw the bandmap the way N1MM draws it
The bandmap was a list of frequencies and callsigns. It is now a frequency
scale with the stations written out beside it and a leader line from each
callsign back to where the station really is.

BandmapLayout does the placing and has no UI reference, so the rule is unit
tested: put the label centred on its frequency, and if it would cover the one
above, push it below instead. That is N1MM's CalculateOffset, and it is what
keeps three stations a hundred hertz apart readable.

The scale stops short of the top and bottom edges, or the first and last
frequency numbers come out cut in half.

VfoMarker draws the receiver as a bar as wide as the mode passes, rather than a
line. Only the receiver is fed: the radio does not report a transmit VFO or a
second radio yet, so VfoRole has the other two roles ready and nothing draws
them. Band-plan colouring of the scale is left out because the segments differ
by ITU region and there is no band-plan table to read them from.

Checked by running the program against a fake cluster node under Xvfb and
looking at the result: twelve spots, three of them stacked with fanned leaders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 16:15:01 +00:00
2026-08-27 15:32:32 +00:00

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
Windows entry, log, check, bandmap, 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 hamlib rigctld, 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.

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. 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 green bar on the scale is the receiver, as wide as the mode it is in. VfoRole has transmit and second-radio bars ready, but nothing feeds them yet: the radio does not report a transmit VFO or a second radio, so those bars stay out rather than showing a guess.

Band-plan colouring of the scale is not there. The segments differ by ITU region and the program has no band-plan table, so it would be guesswork.

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
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 What the operator is typing and what the log says about it — 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.

Not yet: voice keying, QTC handling for WAE, call history files, digital modes beyond logging them, and the check window's Call History and Exchange columns, which are left out rather than shown empty.

The radio, network and keyer clients are tested against fakes that speak the documented protocols. None has been run against a real radio or keyer. The cluster client is tested over a real socket against a node fake that sends the telnet negotiation, the login prompt and spot lines, but not against a live node.

Description
A blatant N1MM ripoff, vibe-coded on top of that
Readme 8.7 MiB
Languages
C# 97.8%
C++ 2.1%