fix(udp): two copies of MSHV shared one dial frequency
FT8 decodes made on 14.095 were labelled 2190 m. That band is 135.7-137.8 kHz, and every affected decode's audio offset plus 136.1 kHz lands inside it — while the ones shown with no band at all land just above 137.8. The decodes were being stamped with another instance's dial. WSJT-X refuses to start a second instance without --rig-name, so its ids differ and keying on the id worked. MSHV has no such rule: both copies call themselves 'MSHV', so the LF instance's Status overwrote the HF one's dial, T/R period and mode. Instances are now identified by their sending socket as well as their name — the socket is stable for as long as the program runs, which is exactly the lifetime this has to hold over. A second application claiming an id already in use is shown as 'MSHV #2' and says so in the log, so the decodes panel can still split them. This also fixes replies: a Reply was addressed to whichever copy sent Status last, which on a two-instance station is a coin toss.
This commit is contained in:
@@ -212,6 +212,21 @@ type Server struct {
|
||||
// lastFrom is the address each program's packets arrive from — where a Reply
|
||||
// has to be sent. See SendReply.
|
||||
lastFrom map[string]*net.UDPAddr
|
||||
// instLabel names each running application, keyed by id AND sending address.
|
||||
//
|
||||
// WSJT-X requires --rig-name for a second instance, so its ids differ. MSHV
|
||||
// does not: two copies both call themselves "MSHV", and everything below was
|
||||
// keyed on that name alone — so the second copy's Status overwrote the
|
||||
// first's dial frequency, and decodes from the 20 m instance were placed at
|
||||
// the LF instance's frequency. Reported as FT8 decodes on 14.095 labelled
|
||||
// 2190 m, which is exactly 136 kHz plus an audio offset.
|
||||
//
|
||||
// The label is what the rest of OpsLog sees: the plain id for the first
|
||||
// instance, then "MSHV #2", "MSHV #3"… so the decodes panel can still tell
|
||||
// them apart on screen.
|
||||
instLabel map[string]string
|
||||
// instSeq counts how many distinct instances have claimed each id.
|
||||
instSeq map[string]int
|
||||
// lastMode is the mode NAME from each program's last Status, used to resolve
|
||||
// a Decode's one-character mode marker.
|
||||
lastMode map[string]string
|
||||
@@ -374,6 +389,40 @@ func (s *Server) run() {
|
||||
}
|
||||
}
|
||||
|
||||
// instanceLabel returns the name for one running application, allocating it on
|
||||
// first sight. Caller holds s.mu.
|
||||
//
|
||||
// The address is part of the identity because the id is not enough: two copies
|
||||
// of MSHV send the same id from different sockets, and the two are different
|
||||
// radios on different bands. The port is included — a program keeps its socket
|
||||
// for as long as it runs, which is exactly the lifetime this has to be stable
|
||||
// over.
|
||||
func (s *Server) instanceLabel(id string, remote *net.UDPAddr) string {
|
||||
if id == "" {
|
||||
return ""
|
||||
}
|
||||
if remote == nil {
|
||||
return id
|
||||
}
|
||||
key := id + "|" + remote.String()
|
||||
if s.instLabel == nil {
|
||||
s.instLabel = map[string]string{}
|
||||
s.instSeq = map[string]int{}
|
||||
}
|
||||
if lbl, ok := s.instLabel[key]; ok {
|
||||
return lbl
|
||||
}
|
||||
s.instSeq[id]++
|
||||
lbl := id
|
||||
if n := s.instSeq[id]; n > 1 {
|
||||
lbl = fmt.Sprintf("%s #%d", id, n)
|
||||
applog.Printf("udp: [%s] a second application calls itself %q (from %s) — it will be shown as %q",
|
||||
s.cfg.Name, id, remote, lbl)
|
||||
}
|
||||
s.instLabel[key] = lbl
|
||||
return lbl
|
||||
}
|
||||
|
||||
// logBadPacket reports a datagram this listener could not parse, with enough of
|
||||
// it to identify the sender — then falls silent.
|
||||
//
|
||||
@@ -464,14 +513,15 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
// to it. Per PROGRAM, not per listener: two receivers share one multicast
|
||||
// group, and a reply must reach the one that heard the station — and it
|
||||
// must go to the sender's own address, never to the group.
|
||||
if w.ProgramID != "" && remote != nil {
|
||||
s.mu.Lock()
|
||||
s.mu.Lock()
|
||||
inst := s.instanceLabel(w.ProgramID, remote)
|
||||
if inst != "" && remote != nil {
|
||||
if s.lastFrom == nil {
|
||||
s.lastFrom = map[string]*net.UDPAddr{}
|
||||
}
|
||||
s.lastFrom[w.ProgramID] = remote
|
||||
s.mu.Unlock()
|
||||
s.lastFrom[inst] = remote
|
||||
}
|
||||
s.mu.Unlock()
|
||||
// Status carries the current dial frequency; remember it so Decode audio
|
||||
// offsets can be turned into RF frequencies for the panadapter.
|
||||
if w.FreqHz > 0 && !w.IsDecode {
|
||||
@@ -479,7 +529,7 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
if s.dialHz == nil {
|
||||
s.dialHz = map[string]int64{}
|
||||
}
|
||||
s.dialHz[w.ProgramID] = w.FreqHz
|
||||
s.dialHz[inst] = w.FreqHz
|
||||
// The T/R period travels with Status, and a decode has to be told
|
||||
// which slot it belongs to — so it is remembered per program the
|
||||
// same way the dial is.
|
||||
@@ -487,7 +537,7 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
if s.trPeriod == nil {
|
||||
s.trPeriod = map[string]int{}
|
||||
}
|
||||
s.trPeriod[w.ProgramID] = w.TRPeriod
|
||||
s.trPeriod[inst] = w.TRPeriod
|
||||
}
|
||||
// The mode NAME, which only Status carries: a Decode gives the
|
||||
// one-character marker instead. See DecodeModeName.
|
||||
@@ -495,7 +545,7 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
if s.lastMode == nil {
|
||||
s.lastMode = map[string]string{}
|
||||
}
|
||||
s.lastMode[w.ProgramID] = w.Mode
|
||||
s.lastMode[inst] = w.Mode
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@@ -507,13 +557,13 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
ev.TxMessage = w.TxMessage
|
||||
ev.Transmitting = w.Transmitting
|
||||
ev.DECall = w.DECall
|
||||
ev.ProgramID = w.ProgramID
|
||||
ev.ProgramID = inst
|
||||
}
|
||||
if w.IsDecode {
|
||||
s.mu.Lock()
|
||||
dial := s.dialHz[w.ProgramID]
|
||||
tr := s.trPeriod[w.ProgramID]
|
||||
statusMode := s.lastMode[w.ProgramID]
|
||||
dial := s.dialHz[inst]
|
||||
tr := s.trPeriod[inst]
|
||||
statusMode := s.lastMode[inst]
|
||||
s.mu.Unlock()
|
||||
if dial <= 0 {
|
||||
// No Status from THIS instance yet. Guessing with another
|
||||
@@ -534,7 +584,7 @@ func (s *Server) handle(pkt []byte, remote *net.UDPAddr) {
|
||||
ev.DecodeTRPeriod = tr
|
||||
ev.DecodeDial = dial
|
||||
ev.DecodeOffAir = w.OffAir
|
||||
ev.ProgramID = w.ProgramID
|
||||
ev.ProgramID = inst
|
||||
ev.DecodeDT = w.DeltaTime
|
||||
ev.DecodeAudioHz = w.DeltaFreqHz
|
||||
ev.DecodeMs = w.DecodeMsSinceMidnight
|
||||
|
||||
Reference in New Issue
Block a user