Compare commits

...
2 Commits
Author SHA1 Message Date
rouggy 9cc72c7575 fix: OmniRig reads VFO A first (FreqA→Freq fallback), fixing IC-7610 CAT
The 7610-only FreqA gate broke CAT on the 7610 when FreqA was momentarily 0.
Match DXHunter/WSJT-X: read FreqA first for all rigs, fall back to the generic
Freq (IC-9100 etc.), then FreqB. OmniRig's generic Freq maps to VFO B on the
7610, which is why keying off FreqA is correct.
2026-07-20 10:11:10 +02:00
rouggy 9e4f43f648 fix: offline operators removed from live_status + award-column visibility
Live status: when an operator goes offline (no QSO in the window) OpsLog now
DELETEs its live_status row instead of just flipping online=0, so a status page
that lists present/recent rows shows them as gone without having to read the
online column — the whole point of the feature. The row reappears on the next
QSO.

Recent QSOs columns: toggling an award column (e.g. DDFM) rebuilt columnDefs and
made AG Grid re-apply every colDef hide default, so hidden non-award columns
(QTH/Grid) came back and restoringRef stayed stuck true (saving off). The
restore-saved-state effect now also runs on awardShown changes, so the other
columns keep the user's visibility.
2026-07-20 09:55:14 +02:00
3 changed files with 36 additions and 41 deletions
+8 -6
View File
@@ -377,11 +377,13 @@ export function RecentQSOsGrid({ rows, selectAllSignal, storageKey, onRowDoubleC
if (state) saveState(colStateKey, stripAwardCols(state)); if (state) saveState(colStateKey, stripAwardCols(state));
}, []); }, []);
// The award columns load asynchronously; when they arrive (or change) the // columnDefs is rebuilt whenever the award columns load OR the user toggles an
// columnDefs memo is rebuilt and AG Grid re-applies each colDef's `hide` // award column (both change the memo restoringRef flips true at line 316). Each
// default — wiping the user's saved visibility (award columns reappear, // rebuild makes AG Grid re-apply every colDef's `hide` default, wiping the user's
// manually-shown ones like LoTW sent vanish). Re-apply the saved state after // saved visibility of the NON-award columns (QTH/Grid reappear, a manually-shown
// every rebuild so the user's choices win. No-op before the grid is ready. // LoTW-sent vanishes). Re-apply the saved (award-stripped) state after EVERY such
// rebuild — hence awardShown in the deps, not just awardCols; without it, toggling
// an award reset the other columns AND left restoringRef stuck true (saving off).
useEffect(() => { useEffect(() => {
const api = gridRef.current?.api; const api = gridRef.current?.api;
const local = loadLocal(colStateKey); const local = loadLocal(colStateKey);
@@ -389,7 +391,7 @@ export function RecentQSOsGrid({ rows, selectAllSignal, storageKey, onRowDoubleC
// Re-enable saving once AG Grid has settled the column events from the rebuild. // Re-enable saving once AG Grid has settled the column events from the rebuild.
const t = window.setTimeout(() => { restoringRef.current = false; }, 0); const t = window.setTimeout(() => { restoringRef.current = false; }, 0);
return () => window.clearTimeout(t); return () => window.clearTimeout(t);
}, [awardCols]); }, [awardCols, awardShown]);
function handleRowDoubleClicked(e: RowDoubleClickedEvent<QSOForm>) { function handleRowDoubleClicked(e: RowDoubleClickedEvent<QSOForm>) {
if (e.data && onRowDoubleClicked) onRowDoubleClicked(e.data); if (e.data && onRowDoubleClicked) onRowDoubleClicked(e.data);
+11 -22
View File
@@ -87,14 +87,6 @@ func (o *OmniRig) Connect() error {
return nil return nil
} }
// isIC7610 reports whether the connected rig is an IC-7610. OmniRig's generic
// Freq property reads the wrong VFO on the 7610 (its Main/Sub model confuses the
// stock ini), so we read VFO A explicitly for it instead — matching what Log4OM
// shows.
func (o *OmniRig) isIC7610() bool {
return strings.Contains(strings.ToUpper(o.rigType), "7610")
}
func (o *OmniRig) Disconnect() { func (o *OmniRig) Disconnect() {
if o.rig != nil { if o.rig != nil {
o.rig.Release() o.rig.Release()
@@ -204,23 +196,20 @@ func (o *OmniRig) ReadState() (RigState, error) {
s.FreqHz, s.RxFreqHz = freqB, freqA s.FreqHz, s.RxFreqHz = freqB, freqA
} }
} else { } else {
// Simplex: the operating frequency is OmniRig's generic Freq (the active // Simplex: read VFO A first, fall back to the generic Freq — exactly like
// VFO), like Log4OM. Fall back to the per-VFO value only if Freq is 0. // DXHunter/WSJT-X. PM_FREQA rigs (Yaesu, Kenwood) populate FreqA; some
// Icoms (IC-9100 etc.) only populate the generic Freq. On the IC-7610
// OmniRig's generic Freq reports VFO B (its Main/Sub model confuses the
// stock ini), so keying off FreqA gives the operator the VFO they expect.
s.Split = false s.Split = false
s.RxFreqHz = 0 s.RxFreqHz = 0
s.FreqHz = freqMain switch {
// IC-7610 quirk: OmniRig's generic Freq reports VFO B (its Main/Sub model case freqA != 0:
// confuses the stock ini), so OpsLog showed the wrong VFO. Read VFO A
// explicitly for the 7610 — what the operator actually wants to see.
if o.isIC7610() && freqA != 0 {
s.FreqHz = freqA s.FreqHz = freqA
} case freqMain != 0:
if s.FreqHz == 0 { s.FreqHz = freqMain
if s.Vfo == "B" || s.Vfo == "BB" { default:
s.FreqHz = freqB s.FreqHz = freqB
} else {
s.FreqHz = freqA
}
} }
} }
return s, nil return s, nil
+17 -13
View File
@@ -202,33 +202,37 @@ func (a *App) publishLiveStatus() {
} }
a.liveActMu.Unlock() a.liveActMu.Unlock()
lastQSO := a.liveLastQSOTime() // authoritative (in-memory OR shared DB) lastQSO := a.liveLastQSOTime() // authoritative (in-memory OR shared DB)
// Online = a new contact was logged within the window. An operator who leaves // On air = a new contact was logged within the window. An operator who leaves
// the log open but stops working shows offline after `liveOnlineWindow`; the // the log open but stops working goes offline after `liveOnlineWindow`; the next
// next QSO flips them back on. never-logged (zero time) → offline. // QSO puts them back on. never-logged (zero time) → offline.
online := 0 online := !lastQSO.IsZero() && time.Since(lastQSO) < liveOnlineWindow
var lastQSOArg any
if !lastQSO.IsZero() {
lastQSOArg = lastQSO.UTC()
if time.Since(lastQSO) < liveOnlineWindow {
online = 1
}
}
if err := a.ensureLiveStatusTable(); err != nil { if err := a.ensureLiveStatusTable(); err != nil {
applog.Printf("livestatus: CREATE TABLE failed: %v", err) applog.Printf("livestatus: CREATE TABLE failed: %v", err)
return return
} }
// Offline → REMOVE the row entirely, not just flip a flag: a status page that
// lists the present rows (the common case, keyed on updated_at) then shows the
// operator as gone without having to read the online column. The row reappears
// on the next QSO. This is the whole point — no one shows on air when they're not.
if !online {
if _, err := a.logDb.ExecContext(a.ctx, "DELETE FROM live_status WHERE operator=?", op); err != nil {
applog.Printf("livestatus: offline DELETE failed: %v", err)
}
return
}
lastQSOArg := lastQSO.UTC()
_, err := a.logDb.ExecContext(a.ctx, _, err := a.logDb.ExecContext(a.ctx,
"INSERT INTO live_status (operator, station, freq_hz, band, mode, online, version, last_qso_at, updated_at) "+ "INSERT INTO live_status (operator, station, freq_hz, band, mode, online, version, last_qso_at, updated_at) "+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP()) "+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP()) "+
"ON DUPLICATE KEY UPDATE station=VALUES(station), freq_hz=VALUES(freq_hz), "+ "ON DUPLICATE KEY UPDATE station=VALUES(station), freq_hz=VALUES(freq_hz), "+
"band=VALUES(band), mode=VALUES(mode), online=VALUES(online), version=VALUES(version), "+ "band=VALUES(band), mode=VALUES(mode), online=VALUES(online), version=VALUES(version), "+
"last_qso_at=VALUES(last_qso_at), updated_at=UTC_TIMESTAMP()", "last_qso_at=VALUES(last_qso_at), updated_at=UTC_TIMESTAMP()",
op, station, freqHz, band, mode, online, appVersion, lastQSOArg) op, station, freqHz, band, mode, 1, appVersion, lastQSOArg)
if err != nil { if err != nil {
applog.Printf("livestatus: INSERT failed: %v", err) applog.Printf("livestatus: INSERT failed: %v", err)
return return
} }
applog.Printf("livestatus: published op=%s station=%s %dHz %s %s online=%d", op, station, freqHz, band, mode, online) applog.Printf("livestatus: published op=%s station=%s %dHz %s %s ON AIR", op, station, freqHz, band, mode)
} }
// LiveStation is one operator's live status for the multi-op "who's on air" widget. // LiveStation is one operator's live status for the multi-op "who's on air" widget.