fix(logging): name the antenna disagreement, and confirm an outbound send

Two silences from one operator's log.

The SteppIR is not blocked — it is being overruled. Every tune is commanded,
acknowledged on the next poll with the frequency asked for, then replaced on
the poll after by a different one: 21075 asked, 21075 confirmed, 21050
reported, again and again, with the operator tuning once more each time. Two
minutes of that reads as a dead link. It now says what it is, once, when the
motors have stopped and the reported frequency is more than 10 kHz from the
commanded one — a controller quantising to its own grid is not a fault, one
sitting somewhere else entirely is.

UDP outbound had no positive signal at all: only failures were logged. When
an operator sets up an ADIF message to a second logger and nothing arrives,
"OpsLog never sent it" and "the other program did not take it" looked exactly
the same from here — and the first is the common case, because a row created
as an inbound ADIF listener instead of an outbound ADIF message matches
nothing and emits nothing. Successful sends are logged with their destination
and service, and a QSO logged with no outbound row says so once a session.
This commit is contained in:
2026-08-14 17:37:12 +02:00
parent 7470ebf47e
commit c5c9d355ec
5 changed files with 141 additions and 3 deletions
+26 -1
View File
@@ -86,12 +86,35 @@ func (m *Manager) EmitLoggedADIF(adif string) {
if strings.TrimSpace(adif) == "" {
return
}
for _, c := range m.Outbound(ServiceDBUpdated) {
rows := m.Outbound(ServiceDBUpdated)
if len(rows) == 0 {
// Said once per session, not per QSO. An operator who configured a second
// logger and sees nothing arrive needs to know the difference between "we
// sent it" and "there was nothing to send to" — and the usual cause is a
// row created as an inbound ADIF listener instead of an outbound message.
m.noADIFOnce.Do(func() {
applog.Printf("udp: a QSO was logged but no outbound \"ADIF message\" row is enabled — " +
"nothing is forwarded to another logger")
})
return
}
for _, c := range rows {
m.sendTo(c, []byte(adif))
}
}
// sendTo resolves the row's destination (host:port) and fires one datagram.
//
// A successful send is logged, not just a failure. UDP has no delivery report:
// when an operator says "I set up an ADIF message to Logger32 on port 2250 and
// nothing arrives", the only thing that separates "OpsLog never sent it" from
// "the other program did not take it" is a line saying we sent. Without one,
// both look identical from here — and the first is far more common, because a
// row created as an INBOUND ADIF listener rather than an OUTBOUND ADIF message
// matches nothing and emits nothing, in silence.
//
// Rate is not a concern: these fire on a QSO being logged or a frequency
// change, not per packet on a socket.
func (m *Manager) sendTo(c Config, payload []byte) {
host := strings.TrimSpace(c.DestinationIP)
if host == "" {
@@ -100,5 +123,7 @@ func (m *Manager) sendTo(c Config, payload []byte) {
dst := fmt.Sprintf("%s:%d", host, c.Port)
if err := SendUDP(dst, payload); err != nil {
applog.Printf("udp: [%s] outbound send to %s failed: %v", c.Name, dst, err)
return
}
applog.Printf("udp: [%s] sent %d bytes to %s (%s)", c.Name, len(payload), dst, c.ServiceType)
}