feat(antgenius): the selected antenna can fill MY_ANTENNA
A station with a switch knows something the log does not: which antenna is actually connected. The working conditions hold what was PLANNED for the band, and stay right until the operator throws the switch — after which every QSO keeps claiming the other antenna. Optional, and off by default: a station that names its antennas differently in the two places would otherwise find its log quietly rewritten. The name written is the one configured on the device, since that is the name the operator gave it and the one they will look for. WHICH PORT is the real problem, and the reason this is not a one-liner. The switch has two, the radio has two jacks, and the QSO went out through one of them; naming the wrong port's antenna is worse than naming the band default, because it looks authoritative. The radio's own TX antenna selection decides, and the jack-to-port wiring is a setting — it is the station's cabling and neither device can report it. When the port cannot be told — a transverter jack, a rig that reports nothing, both switch ports live — nothing is written and the band default stands. Silence beats a confident guess in a field nobody re-checks.
This commit is contained in:
@@ -781,8 +781,8 @@ type App struct {
|
||||
// so closing the QSL Manager — or starting another download — stops the previous
|
||||
// one instead of leaving it running against the app-lifetime context (which made
|
||||
// a still-running QRZ sync bleed its log into a freshly started LoTW download).
|
||||
confDLMu sync.Mutex
|
||||
confDLCancel context.CancelFunc
|
||||
confDLMu sync.Mutex
|
||||
confDLCancel context.CancelFunc
|
||||
|
||||
// hamlogUnmatched holds the confirmations the last HAMLOG.online import
|
||||
// could not place onto a QSO, kept so they can be exported and worked
|
||||
@@ -790,31 +790,31 @@ type App struct {
|
||||
// accumulated: it describes one run, not a history.
|
||||
hamlogUnmatchedMu sync.Mutex
|
||||
hamlogUnmatched []qso.QSO
|
||||
udpLogMu sync.Mutex // serialises UDP auto-log so concurrent packets can't both pass the dedup check
|
||||
adifMonMu sync.Mutex // guards the ADIF-monitor config (file list + per-file read offsets)
|
||||
syncMu sync.Mutex // serialises folder synchronisation: config, the seq counter, and the append to our own file
|
||||
syncSent int64 // changes written to the folder this session
|
||||
syncReceived int64 // changes taken from the other machines this session
|
||||
syncLast time.Time // last completed pass, for the status panel
|
||||
syncErr string // last folder error, shown in settings — a share that dropped is otherwise invisible
|
||||
relayAutoMu sync.Mutex // serialises relay auto-control evaluation
|
||||
relayAutoLast map[string]bool // deviceID|relay → last applied on/off, so we only switch on a real change
|
||||
relayAutoOn atomic.Bool // cached "auto-control enabled" so the CAT hot path skips work when off
|
||||
relayDrvMu sync.Mutex // guards the cached relay drivers below
|
||||
relayDrv map[string]cachedRelay // deviceID → live driver (reused across polls; stateful boards can't be reopened per call)
|
||||
pttPort serial.Port // open serial port while PTT (RTS/DTR) is asserted
|
||||
pttKeyedMethod string // "cat" | "rts" | "dtr" while keyed; "" when idle
|
||||
pttGen int64 // bumped on every key; a delayed unkey only fires if unchanged (guards against a stale release cutting a new transmission)
|
||||
startupErr string // captured for surfacing to the frontend
|
||||
settingsScoped atomic.Bool // true once a.settings is scoped to the active profile — GetUIPref/SetUIPref (per-profile) must wait for it, else an early call reads the wrong scope and e.g. resets the theme
|
||||
dbPath string // settings/config database file (settings + profiles); may be a user-chosen location
|
||||
logbookPath string // default SQLite logbook file (QSOs), next to the settings db — used when a profile doesn't point elsewhere
|
||||
logDbPath string // resolved SQLite file actually backing logDb ("" on MySQL, or when the settings db serves as the logbook) — the file the backup snapshots
|
||||
logDb *sql.DB // QSO logbook connection — MySQL, a per-profile SQLite file, or the default logbook.db (never the settings db, except on fallback)
|
||||
dbBackend string // "sqlite" | "mysql" — the logbook backend actually opened at startup
|
||||
dbBackendErr string // non-empty when a configured MySQL backend failed and we fell back to SQLite
|
||||
offlineQ *offlineq.Queue // ADIF outbox: QSOs logged while the DB was unreachable
|
||||
offlineMode bool // last write failed because the DB was unreachable
|
||||
udpLogMu sync.Mutex // serialises UDP auto-log so concurrent packets can't both pass the dedup check
|
||||
adifMonMu sync.Mutex // guards the ADIF-monitor config (file list + per-file read offsets)
|
||||
syncMu sync.Mutex // serialises folder synchronisation: config, the seq counter, and the append to our own file
|
||||
syncSent int64 // changes written to the folder this session
|
||||
syncReceived int64 // changes taken from the other machines this session
|
||||
syncLast time.Time // last completed pass, for the status panel
|
||||
syncErr string // last folder error, shown in settings — a share that dropped is otherwise invisible
|
||||
relayAutoMu sync.Mutex // serialises relay auto-control evaluation
|
||||
relayAutoLast map[string]bool // deviceID|relay → last applied on/off, so we only switch on a real change
|
||||
relayAutoOn atomic.Bool // cached "auto-control enabled" so the CAT hot path skips work when off
|
||||
relayDrvMu sync.Mutex // guards the cached relay drivers below
|
||||
relayDrv map[string]cachedRelay // deviceID → live driver (reused across polls; stateful boards can't be reopened per call)
|
||||
pttPort serial.Port // open serial port while PTT (RTS/DTR) is asserted
|
||||
pttKeyedMethod string // "cat" | "rts" | "dtr" while keyed; "" when idle
|
||||
pttGen int64 // bumped on every key; a delayed unkey only fires if unchanged (guards against a stale release cutting a new transmission)
|
||||
startupErr string // captured for surfacing to the frontend
|
||||
settingsScoped atomic.Bool // true once a.settings is scoped to the active profile — GetUIPref/SetUIPref (per-profile) must wait for it, else an early call reads the wrong scope and e.g. resets the theme
|
||||
dbPath string // settings/config database file (settings + profiles); may be a user-chosen location
|
||||
logbookPath string // default SQLite logbook file (QSOs), next to the settings db — used when a profile doesn't point elsewhere
|
||||
logDbPath string // resolved SQLite file actually backing logDb ("" on MySQL, or when the settings db serves as the logbook) — the file the backup snapshots
|
||||
logDb *sql.DB // QSO logbook connection — MySQL, a per-profile SQLite file, or the default logbook.db (never the settings db, except on fallback)
|
||||
dbBackend string // "sqlite" | "mysql" — the logbook backend actually opened at startup
|
||||
dbBackendErr string // non-empty when a configured MySQL backend failed and we fell back to SQLite
|
||||
offlineQ *offlineq.Queue // ADIF outbox: QSOs logged while the DB was unreachable
|
||||
offlineMode bool // last write failed because the DB was unreachable
|
||||
|
||||
catFlexSpots bool // push cluster spots to the FlexRadio panadapter
|
||||
catFlexDVKDax bool // raise the Flex transmit DAX button around a voice message
|
||||
@@ -3330,6 +3330,9 @@ func (a *App) applyStationDefaults(q *qso.QSO, includeIdentity bool) {
|
||||
if q.MyAntenna == "" {
|
||||
q.MyAntenna = p.MyAntenna
|
||||
}
|
||||
// The switch has the last word, when asked to: everything above is what was
|
||||
// PLANNED for this band, and the Antenna Genius knows what is connected.
|
||||
a.applyAntGeniusAntenna(&q.MyAntenna)
|
||||
if q.TXPower == nil && p.TxPower != nil {
|
||||
v := *p.TxPower
|
||||
q.TXPower = &v
|
||||
@@ -17653,6 +17656,12 @@ type AntGeniusSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
Password string `json:"password"` // remote-access password; leave blank on LAN (no AUTH)
|
||||
// UseForMyAntenna stamps the SELECTED antenna's name into MY_ANTENNA on
|
||||
// every QSO logged, ahead of the band default from Operating conditions.
|
||||
UseForMyAntenna bool `json:"use_for_my_antenna"`
|
||||
// Ant1Port says which switch port the radio's ANT1 jack is wired to
|
||||
// (1 = A, 2 = B). Station wiring; neither device can report it.
|
||||
Ant1Port int `json:"ant1_port"`
|
||||
}
|
||||
|
||||
// GetAntGeniusSettings returns the persisted Antenna Genius config.
|
||||
@@ -17661,13 +17670,19 @@ func (a *App) GetAntGeniusSettings() (AntGeniusSettings, error) {
|
||||
if a.settings == nil {
|
||||
return out, fmt.Errorf("db not initialized")
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx, keyAntGeniusEnabled, keyAntGeniusHost, keyAntGeniusPassword)
|
||||
m, err := a.settings.GetMany(a.ctx, keyAntGeniusEnabled, keyAntGeniusHost, keyAntGeniusPassword,
|
||||
keyAntGeniusMyAnt, keyAntGeniusPortForA1)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Enabled = m[keyAntGeniusEnabled] == "1"
|
||||
out.Host = m[keyAntGeniusHost]
|
||||
out.Password = m[keyAntGeniusPassword]
|
||||
out.UseForMyAntenna = m[keyAntGeniusMyAnt] == "1"
|
||||
out.Ant1Port = 1
|
||||
if m[keyAntGeniusPortForA1] == "2" {
|
||||
out.Ant1Port = 2
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -17677,9 +17692,11 @@ func (a *App) SaveAntGeniusSettings(s AntGeniusSettings) error {
|
||||
return fmt.Errorf("db not initialized")
|
||||
}
|
||||
for k, v := range map[string]string{
|
||||
keyAntGeniusEnabled: boolStr(s.Enabled),
|
||||
keyAntGeniusHost: strings.TrimSpace(s.Host),
|
||||
keyAntGeniusPassword: s.Password,
|
||||
keyAntGeniusEnabled: boolStr(s.Enabled),
|
||||
keyAntGeniusHost: strings.TrimSpace(s.Host),
|
||||
keyAntGeniusPassword: s.Password,
|
||||
keyAntGeniusMyAnt: boolStr(s.UseForMyAntenna),
|
||||
keyAntGeniusPortForA1: map[bool]string{true: "2", false: "1"}[s.Ant1Port == 2],
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, k, v); err != nil {
|
||||
return err
|
||||
@@ -20343,8 +20360,6 @@ func clampSpotMax(n int) int {
|
||||
return n
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GetSpotTTLMinutes returns how long a spot stays in the list, in minutes.
|
||||
// 0 means spots are kept until the count cap pushes them out, which is what
|
||||
// OpsLog always did.
|
||||
|
||||
Reference in New Issue
Block a user