fix(udp): several FT8 programs no longer fight over the callsign field
Reported from a station running MSHV, WSJT-X and JTDX together: click a call in MSHV and the entry field filled, emptied, refilled — once a second — with the map zooming in and out to match. Two causes, both about reading one program's statement as another's. The clear was tracked per LISTENER. Several decoders commonly share one multicast group, so an idle WSJT-X reporting no DX Call — which is simply true, and which it repeats every second — was read as MSHV abandoning the station it was calling. "The operator cleared the DX Call" is a statement about one program, never about a socket, so it is now tracked per program, and a clear carries the id of whoever made it. And nothing arbitrated between them. The program that announces a station now holds the entry field, and the others cannot touch it until it lets go: it clears its own call, it stops sending (closed), or the QSO is logged. That is the operator's own suggestion, and it is the right one — between overs there is no way to tell "I have nothing" from "I am not the one you are working" except by remembering who was. Refusing another program's callsign is logged once per focus, not once a second: an operator whose second decoder "stopped filling the call" needs something to read.
This commit is contained in:
@@ -885,6 +885,10 @@ type App struct {
|
||||
|
||||
alertStore *alerts.Store // DX-cluster spot alert rules (global JSON)
|
||||
|
||||
// udpFocus arbitrates the entry field between several decoders running at
|
||||
// once — see app_udp_focus.go.
|
||||
udpFocus udpFocus
|
||||
|
||||
// Satellites. The elements (where the birds are) and the frequency plan
|
||||
// (what to do with the radio) are held apart because they come from
|
||||
// different places and change for different reasons — a feed every few
|
||||
@@ -3132,6 +3136,9 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) {
|
||||
}
|
||||
if err == nil {
|
||||
q.ID = id
|
||||
// The contact is over, so no decoder holds the entry field any more: the
|
||||
// next station may come from whichever one hears it first.
|
||||
a.udpFocus.release("QSO logged")
|
||||
a.noteWorked(q.Callsign, q.Band, q.Mode) // keep the alert worked-index fresh (in-memory)
|
||||
a.noteLiveQSO() // multi-op: flip this operator back "online" (publishes async)
|
||||
// Snapshot the QSO recording SYNCHRONOUSLY, BEFORE announcing the log: the
|
||||
@@ -14616,6 +14623,13 @@ func (a *App) consumeUDPEvents() {
|
||||
"adif": ev.LoggedADIF,
|
||||
})
|
||||
case ev.ClearCall:
|
||||
// Only from the program the entry field belongs to. An idle decoder
|
||||
// alongside the one being worked clears its own DX Call for reasons
|
||||
// of its own, and that must not empty a field somebody else filled.
|
||||
if !a.udpFocus.holds(ev.ProgramID) {
|
||||
break
|
||||
}
|
||||
a.udpFocus.release("DX Call cleared")
|
||||
applog.Printf("udp: emit udp:clear_call (DX Call cleared in the digital app)\n")
|
||||
wruntime.EventsEmit(a.ctx, "udp:clear_call", map[string]any{
|
||||
"service": string(ev.Service),
|
||||
@@ -14640,6 +14654,12 @@ func (a *App) consumeUDPEvents() {
|
||||
wruntime.EventsEmit(a.ctx, "udp:remote_call", ev.DXCall)
|
||||
}
|
||||
case ev.DXCall != "":
|
||||
// With two or three decoders running, the one announcing a station
|
||||
// takes the entry field and keeps it until it lets go. See udpFocus.
|
||||
if !a.udpFocus.claim(ev.ProgramID) {
|
||||
a.udpFocus.noteIgnored(ev.ProgramID, ev.DXCall)
|
||||
break
|
||||
}
|
||||
applog.Printf("udp: emit udp:dx_call %q (mode=%s freq=%d)\n", ev.DXCall, ev.Mode, ev.FreqHz)
|
||||
wruntime.EventsEmit(a.ctx, "udp:dx_call", map[string]any{
|
||||
"call": ev.DXCall,
|
||||
|
||||
Reference in New Issue
Block a user