feat: HAMLOG.online upload and confirmations, Yaesu antenna, a named port holder
HAMLOG.online is a seventh external service: one API key, one ADIF record per QSO, immediate / delayed / on-close like the rest. They publish no API documentation, so the protocol is read from THEIR OWN client — the HAMLOG Agent (github.com/hamlogonline/Agent), which is the authoritative source short of asking them: POST https://hamlog.online/api/agent/ {"ADIFADD": {"APIKEY": k, "ADIFDATA": record}} → {"STATUS":"OK"} {"KEYSTATUS": {"APIKEY": k}} → {"STATUS":"OK","CALLSIGN":…} Success is STATUS == OK, not "no ERROR field": their failure carries ERROR and no STATUS, and reading an unknown reply — a proxy page, a maintenance notice — as an acceptance is how a contact goes missing without anyone noticing. KEYSTATUS buys something no other service here offers: the key can be checked BEFORE the first QSO, and the answer names the account. A key pasted from another callsign is caught in the settings panel rather than through a week of silent refusals. Their confirmations are also an award source now, ticked like LoTW rather than expressed through "custom". It reads the ADIF extras, not a column: the standard names a field for hamlog.EU and none for hamlog.ONLINE, and borrowing the other site's field would write a falsehood into every exported log. Three plausible key names from their own export are accepted too, so nobody has to rename a column by hand after an export. Yaesu gains antenna selection (AN), remembered per band — the antenna picked on a band comes back with it, with no table to fill in anywhere. Rigs with one socket never answer AN and never show the row; the log says which case it is. And a serial port that is refused now names its likely holder. OmniRig stays resident once activated and keeps the port of the rig configured in it, so a native backend never gets it — "Serial port busy" alone accused nobody, and an FTDX10 spent a morning being blamed for it.
This commit is contained in:
@@ -19882,6 +19882,86 @@ func (a *App) SetYaesuPreamp(n int) error {
|
||||
return a.yaesuDo(func(y cat.YaesuController) error { return y.SetYaesuPreamp(n) })
|
||||
}
|
||||
|
||||
// SetYaesuAntenna selects an antenna jack on a Yaesu, and REMEMBERS it for the
|
||||
// band the rig is on.
|
||||
//
|
||||
// No settings page for this, deliberately. A per-band antenna table is one more
|
||||
// form to fill in and one more menu to find, for a fact the operator states
|
||||
// perfectly well by choosing the antenna once on that band. So the choice is
|
||||
// learnt where it is made, and re-applied on the next visit — see
|
||||
// applyYaesuBandAntenna.
|
||||
func (a *App) SetYaesuAntenna(n int) error {
|
||||
if err := a.yaesuDo(func(y cat.YaesuController) error { return y.SetYaesuAntenna(n) }); err != nil {
|
||||
return err
|
||||
}
|
||||
if band := strings.ToLower(strings.TrimSpace(a.cat.State().Band)); band != "" && n > 0 {
|
||||
m := a.yaesuBandAntennas()
|
||||
if m[band] != n {
|
||||
m[band] = n
|
||||
a.saveYaesuBandAntennas(m)
|
||||
applog.Printf("yaesu: antenna %d remembered for %s", n, band)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyYaesuBandAnt holds the learnt band → antenna map, as JSON.
|
||||
const keyYaesuBandAnt = "yaesu.band_antennas"
|
||||
|
||||
func (a *App) yaesuBandAntennas() map[string]int {
|
||||
out := map[string]int{}
|
||||
if raw := a.settingOr(keyYaesuBandAnt, ""); raw != "" {
|
||||
_ = json.Unmarshal([]byte(raw), &out)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *App) saveYaesuBandAntennas(m map[string]int) {
|
||||
if b, err := json.Marshal(m); err == nil {
|
||||
a.setSetting(keyYaesuBandAnt, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
// GetYaesuBandAntennas exposes the learnt map, so the panel can show which band
|
||||
// carries which antenna without the operator having to change band to find out.
|
||||
func (a *App) GetYaesuBandAntennas() map[string]int { return a.yaesuBandAntennas() }
|
||||
|
||||
// ForgetYaesuBandAntenna drops the memory for one band — the way to undo a
|
||||
// choice made by mistake, without a table to edit.
|
||||
func (a *App) ForgetYaesuBandAntenna(band string) {
|
||||
m := a.yaesuBandAntennas()
|
||||
delete(m, strings.ToLower(strings.TrimSpace(band)))
|
||||
a.saveYaesuBandAntennas(m)
|
||||
}
|
||||
|
||||
// YaesuApplyBandAntenna is applyYaesuBandAntenna as a binding, called from the
|
||||
// same place the Flex one is — a band change or a spot click.
|
||||
func (a *App) YaesuApplyBandAntenna(band string) error {
|
||||
a.applyYaesuBandAntenna(band)
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyYaesuBandAntenna puts the remembered antenna back when the rig changes
|
||||
// band. Silent when nothing was ever learnt for it: an operator who has not
|
||||
// expressed a preference for 17 m must not have their antenna moved.
|
||||
func (a *App) applyYaesuBandAntenna(band string) {
|
||||
b := strings.ToLower(strings.TrimSpace(band))
|
||||
if b == "" || a.cat == nil {
|
||||
return
|
||||
}
|
||||
want := a.yaesuBandAntennas()[b]
|
||||
if want <= 0 {
|
||||
return
|
||||
}
|
||||
_ = a.cat.YaesuDo(func(y cat.YaesuController) error {
|
||||
if st := y.YaesuState(); st.Antenna == want || st.Antenna == 0 {
|
||||
return nil // already there, or a rig with a single socket
|
||||
}
|
||||
applog.Printf("yaesu: band %s → antenna %d (remembered)", b, want)
|
||||
return y.SetYaesuAntenna(want)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) SetYaesuAtt(db int) error {
|
||||
return a.yaesuDo(func(y cat.YaesuController) error { return y.SetYaesuAtt(db) })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user