feat(lookup): optional QRZ nickname as the logged name

QRZ publishes <nickname> — the name an operator goes BY on the air — and
OpsLog was composing fname + name instead. "Bob" is what belongs in a log;
"Robert J Smith" is what belongs on a licence.

QRZ only, and deliberately so: HamQTH's <nick> already fills the Name field
that way, so the same switch there would toggle a behaviour it has no way to
turn off.

A published nickname is optional, so an empty one falls through to the
registered name. That is the whole point of it being a fallback rather than a
swap, and it is what the test pins — a blank nickname must never blank the
name.
This commit is contained in:
2026-08-13 10:49:52 +02:00
parent b2382a6135
commit 8e49d37cbd
9 changed files with 140 additions and 57 deletions
+34 -25
View File
@@ -78,6 +78,7 @@ import (
const (
keyQRZUser = "lookup.qrz.user"
keyQRZPassword = "lookup.qrz.password"
keyQRZNickname = "lookup.qrz.prefer_nickname" // "1" → QRZ <nickname> over first+last name
keyHQUser = "lookup.hamqth.user"
keyHQPassword = "lookup.hamqth.password"
keyCacheTTL = "lookup.cache.ttl_days"
@@ -232,11 +233,11 @@ const (
keyUltrabeamEnabled = "ultrabeam.enabled"
keyUltrabeamHost = "ultrabeam.host"
keyUltrabeamPort = "ultrabeam.port"
keyUltrabeamFollow = "ultrabeam.follow" // "1" → re-tune to the rig frequency
keyUltrabeamStep = "ultrabeam.step_khz" // re-tune hysteresis: 25 | 50 | 100 kHz
keyMotorTrackMode = "motor.track_mode" // "always" | "step" | "band"
keyMotorBandFreqs = "motor.band_freqs" // per-band tune frequency: "40m=7100,20m=14150"
keyChaseNewGrids = "cluster.chase_grids" // "1" → persist learnt locators across restarts
keyUltrabeamFollow = "ultrabeam.follow" // "1" → re-tune to the rig frequency
keyUltrabeamStep = "ultrabeam.step_khz" // re-tune hysteresis: 25 | 50 | 100 kHz
keyMotorTrackMode = "motor.track_mode" // "always" | "step" | "band"
keyMotorBandFreqs = "motor.band_freqs" // per-band tune frequency: "40m=7100,20m=14150"
keyChaseNewGrids = "cluster.chase_grids" // "1" → persist learnt locators across restarts
keyRowColors = "appearance.row_colors"
keyMotorType = "ultrabeam.type" // "ultrabeam" | "steppir" (default ultrabeam)
keyMotorTransport = "ultrabeam.transport" // "tcp" | "serial" (default tcp)
@@ -564,7 +565,11 @@ type LookupSettings struct {
Primary string `json:"primary"`
Failsafe string `json:"failsafe"`
DownloadImages bool `json:"download_images"` // show QRZ profile pictures in the UI
CacheTTLDays int `json:"cache_ttl_days"`
// QRZPreferNickname logs the name an operator goes BY rather than their
// registered first+last. QRZ only: HamQTH's <nick> already serves that role
// there, so the option would be a second switch for a behaviour it has.
QRZPreferNickname bool `json:"qrz_prefer_nickname"`
CacheTTLDays int `json:"cache_ttl_days"`
}
// App is the application context bound to the Wails runtime.
@@ -2596,7 +2601,7 @@ func (a *App) reloadLookupProviders() {
return
}
m, err := a.settings.GetMany(a.ctx,
keyQRZUser, keyQRZPassword, keyHQUser, keyHQPassword,
keyQRZUser, keyQRZPassword, keyQRZNickname, keyHQUser, keyHQPassword,
keyCacheTTL, keyLookupPrimary, keyLookupFailsafe)
if err != nil {
fmt.Println("OpsLog: settings load error:", err)
@@ -2610,7 +2615,9 @@ func (a *App) reloadLookupProviders() {
switch name {
case "qrz":
if m[keyQRZUser] != "" && m[keyQRZPassword] != "" {
return lookup.NewQRZ(m[keyQRZUser], m[keyQRZPassword])
p := lookup.NewQRZ(m[keyQRZUser], m[keyQRZPassword])
p.PreferNickname = m[keyQRZNickname] == "1"
return p
}
case "hamqth":
if m[keyHQUser] != "" && m[keyHQPassword] != "" {
@@ -7198,7 +7205,7 @@ func (a *App) GetLookupSettings() (LookupSettings, error) {
}
m, err := a.settings.GetMany(a.ctx,
keyQRZUser, keyQRZPassword, keyHQUser, keyHQPassword,
keyCacheTTL, keyLookupPrimary, keyLookupFailsafe, keyLookupImages)
keyCacheTTL, keyLookupPrimary, keyLookupFailsafe, keyLookupImages, keyQRZNickname)
if err != nil {
return LookupSettings{}, err
}
@@ -7207,14 +7214,15 @@ func (a *App) GetLookupSettings() (LookupSettings, error) {
ttl = 30
}
return LookupSettings{
QRZUser: m[keyQRZUser],
QRZPassword: m[keyQRZPassword],
HamQTHUser: m[keyHQUser],
HamQTHPassword: m[keyHQPassword],
Primary: m[keyLookupPrimary],
Failsafe: m[keyLookupFailsafe],
DownloadImages: m[keyLookupImages] == "1",
CacheTTLDays: ttl,
QRZUser: m[keyQRZUser],
QRZPassword: m[keyQRZPassword],
HamQTHUser: m[keyHQUser],
HamQTHPassword: m[keyHQPassword],
Primary: m[keyLookupPrimary],
Failsafe: m[keyLookupFailsafe],
DownloadImages: m[keyLookupImages] == "1",
QRZPreferNickname: m[keyQRZNickname] == "1",
CacheTTLDays: ttl,
}, nil
}
@@ -7240,6 +7248,7 @@ func (a *App) SaveLookupSettings(s LookupSettings) error {
keyLookupPrimary: s.Primary,
keyLookupFailsafe: s.Failsafe,
keyLookupImages: boolStr(s.DownloadImages),
keyQRZNickname: boolStr(s.QRZPreferNickname),
} {
if err := a.settings.Set(a.ctx, k, v); err != nil {
return err
@@ -14822,14 +14831,14 @@ func (a steppirAdapter) Status() motorStatus {
// is kept (bindings + frontend) though it now covers SteppIR too.
type UltrabeamSettings struct {
Enabled bool `json:"enabled"`
Type string `json:"type"` // "ultrabeam" | "steppir"
Transport string `json:"transport"` // "tcp" | "serial"
Host string `json:"host"` // tcp
Port int `json:"port"` // tcp
COM string `json:"com"` // serial device
Baud int `json:"baud"` // serial baud
Follow bool `json:"follow"` // re-tune the antenna to the rig's frequency
StepKHz int `json:"step_khz"` // re-tune only when the freq moved this far (25/50/100)
Type string `json:"type"` // "ultrabeam" | "steppir"
Transport string `json:"transport"` // "tcp" | "serial"
Host string `json:"host"` // tcp
Port int `json:"port"` // tcp
COM string `json:"com"` // serial device
Baud int `json:"baud"` // serial baud
Follow bool `json:"follow"` // re-tune the antenna to the rig's frequency
StepKHz int `json:"step_khz"` // re-tune only when the freq moved this far (25/50/100)
// When the follow loop is allowed to move the motors. The three choices the
// SteppIR's own controller software offers, because operators arrive with
// that mental model: