diff --git a/docs/Planned-Features.md b/docs/Planned-Features.md index a2250b72..38d91caa 100644 --- a/docs/Planned-Features.md +++ b/docs/Planned-Features.md @@ -351,6 +351,7 @@ frequencies, not contacts. Neither is the operator's own log. | REQ-FMT-002 | The system shall import ADIF `.adi` files produced by other logging software, preserving fields it does not itself use. | | REQ-FMT-003 | When importing, the system shall identify QSOs already held and shall not duplicate them. | | REQ-FMT-004 | The system shall export a filtered selection of the log as a Cabrillo 3.0 file for contest submission. | +| REQ-LOG-009 | The system shall stamp QSO times from the server's clock in UTC, and shall tell the operator when the browser's clock disagrees with it by more than one second. | | REQ-LAY-001 | The system shall present the logbook in a panel of its own, reachable whatever layout is selected. | | REQ-LAY-002 | The system shall offer a "Ham radio" operator layout presenting the transceiver controls and that panel together. | | REQ-LAY-003 | Where the selected rig cannot transmit, the system shall not offer the ham layout. | @@ -371,7 +372,7 @@ Six fields, and no more (REQ-LOG-002, REQ-LOG-003): | Frequency | the selected rig's dial | `FREQ`, with `BAND` derived from it | | Mode | the selected rig | `MODE`, and `SUBMODE` where the mode implies one | | Rig name | the rig's display name | `MY_RIG` | -| Time | the clock, at the moment the entry opens | `QSO_DATE`, `TIME_ON` | +| Time | the server's clock, UTC, when the entry opens | `QSO_DATE`, `TIME_ON` | | Callsign | the decode row or map station the entry was started from, else empty | `CALL` | | Locator | that station's grid, where the decode carried one, else empty | `GRIDSQUARE` | @@ -409,9 +410,8 @@ is right for a few dozen bookmarks and wrong for a log: a station with 40 000 QS rewrite several megabytes to log one contact, and lose the lot if the power went during the dump. -The log is instead a JSON Lines file — the shape `trx-decode-log` already uses — at -`~/.config/trx-rs/logbook.jsonl`, appended one record per write and read into an in-memory -index at startup. An edit or a delete appends a new revision of that record's id; the load +The log is instead a JSON Lines file — the shape `trx-decode-log` already uses — appended one +record per write and read into an in-memory index at startup. An edit or a delete appends a new revision of that record's id; the load keeps the last one, and a compaction pass rewrites the file when superseded records exceed a threshold. Appending is O(1) and atomic per line, so a crash costs at most the line being written. @@ -451,8 +451,9 @@ put in the exchange there is nothing for it to write. | Source | What it gives the log | How | |--------|----------------------|-----| | `RigState` | `FREQ`, `BAND`, `MODE`/`SUBMODE`, and the rig id a QSO was made on | watch channel already in the frontend context | -| Client config `general.callsign` | `STATION_CALLSIGN`, `OPERATOR` | already surfaced as `owner_callsign` in frontend meta | -| Rig latitude/longitude | `MY_GRIDSQUARE` | `latLonToMaidenhead`, already in the frontend | +| Client config `general.callsign` | `STATION_CALLSIGN`, and the default `OPERATOR` | already surfaced as `owner_callsign` in frontend meta | +| The QSO's own rig, and its position | `MY_GRIDSQUARE` | per-rig latitude and longitude already carried in the rig list | +| Server clock | `QSO_DATE`, `TIME_ON` in UTC | new `GET /logbook/now`, which also feeds the browser-clock check | | Decoder panels and map | a pre-filled entry: callsign, grid, and the report to offer | existing decode history; no new plumbing | | `bandForHz` | `BAND` from a frequency | exists in `map-core.ts`; move to a shared module | @@ -468,6 +469,7 @@ put in the exchange there is nothing for it to write. | `GET` | `/logbook/export.cbr` | Cabrillo export of a contest selection | | `POST` | `/logbook/import` | Import, answering with counts: added, duplicate, rejected | | `GET` | `/logbook/worked/{call}` | Worked-before: bands and modes | +| `GET` | `/logbook/now` | The server's UTC clock, for stamping entries and checking the browser's | Writes require the control role, as the rig endpoints do. @@ -511,14 +513,64 @@ task and the rig is the instrument. | 4 | Ham layout, pre-filled entry from a decode row or the map, worked-before | | 5 | Contest exchange fields and Cabrillo export; QSL and LoTW/eQSL fields; per-band worked/confirmed statistics | -### Open questions +### Decisions -- One station log, or one per rig? The proposal assumes one, with the rig id recorded on each - QSO, since a callsign worked on the second rig is still worked. -- Multiple operators at one station: `OPERATOR` per QSO, or per session? -- Clock: the client's or the server's? The server's is proposed — it is the machine at the - radio — with the offset shown if the browser disagrees by more than a second. -- Should the log file be configurable, or fixed beside the bookmarks? -- Import collisions: the proposed key is callsign, band, mode and time to the minute. Contest - operators work the same station twice in a minute on different bands, which that key allows; - a same-band dupe inside a minute is treated as the same QSO. +**One station log, not one per rig.** Awards and uploads are per station callsign — DXCC, WAS +and LoTW all count the callsign, not the radio — and a station worked on the second rig is +still worked. The rig is recorded on the QSO (`MY_RIG`) rather than dividing the log by it. +The station *location* fields do follow the rig, though: trx-rs rigs can be in different +places, so `MY_GRIDSQUARE` is taken from the rig that made the QSO rather than from one global +setting, which is also what LoTW's station locations expect. + +**The operator is a per-QSO field, set once per session.** ADIF separates `STATION_CALLSIGN` +(the call used on the air) from `OPERATOR` (the person at the key); multi-operator stations +rotate operators through one station callsign, which is why contest loggers record it per QSO. +It is stored per QSO, defaulted from the configured callsign so a single operator never touches +it, and changed on the station line at the top of the panel where it sticks for the session. +It cannot be taken from the session's identity: the auth roles are `control` and `rx`, with no +notion of who is logged in. + +**Server clock, and the log says so.** The server is the machine at the radio; the browser may +be on a phone in another timezone with a clock nobody has checked. QSO times are UTC from the +server, and when a browser's clock disagrees by more than a second the panel says so rather +than silently logging a time the operator did not expect. + +**The log file is configurable, and defaults to the user's data directory.** Bookmarks live in +the config directory because they are settings; decode logs live in the cache directory because +they are disposable. A QSO log is neither — it is irreplaceable, and cache directories are +swept by cleaners. `[logbook].path` in the client config, defaulting to +`dirs::data_dir()/trx-rs/logbook.jsonl`, so a station that keeps its log on a synced or +backed-up volume can say so. + +**Import collisions: callsign, band, mode and a two-minute window.** Two loggers rarely agree +to the second on the same QSO — one records the time the contact started, another the time it +was entered — so an exact-minute key duplicates half of what it is asked to merge. Two minutes +absorbs that. It does not swallow legitimate re-works: contest rules forbid a second contact +with the same station on the same band and mode, so a repeat inside two minutes is the same +QSO. Times are compared as instants rather than date and time strings, so a QSO either side of +midnight matches. Modes are normalised before comparison, or a log that stored `SSB` would fail +to match ours that stored `USB`. + +### Rig modes to ADIF modes + +The rig reports what it is demodulating; ADIF wants what the contact was made on, which is not +always the same word: + +| Rig mode | ADIF `MODE` | ADIF `SUBMODE` | +|----------|-------------|----------------| +| `USB`, `LSB` | `SSB` | `USB` / `LSB` | +| `CW`, `CWR` | `CW` | — | +| `AM`, `SAM` | `AM` | — | +| `FM`, `WFM` | `FM` | — | +| `PKT` | `PKT` | — | +| `DIG` | decided by the decoder in use, not by the rig | | +| `AIS`, `VDES` | none — not amateur modes, and these rigs do not log | | +| `Other(..)` | passed through when it names an ADIF mode, else left for the operator | | + +`DIG` is the one that cannot come from the rig: a rig in `DIG` is in FT8, FT4 or something else +depending on which decoder is running, and an entry started from an FT8 row logs `FT8` rather +than the rig's word for it. WSPR never opens an entry at all — it is a beacon mode, and hearing +a beacon is not a contact. + +The table is data in `qso.rs`, checked against the ADIF enumeration when it is written, with +anything unrecognised left to the operator rather than guessed into the log.